#!/usr/bin/env python
#-------------------------------python-----------------------pycart.py--#
#                                                                       #
#                          Piano cartesiano                             #
#                                                                       #
#--Daniele Zambelli-----------Licence GPL------------------------2007---#

# Serve piu' documentazione dei metodi !

import Tkinter as tk
#from math import *
import os
import re
import pygrapherror as pyerr

_pycart_version = '02.03.02'

_RENUMERO=re.compile('\#((\d|[a-f]){2}){3}$')

def _inff(n):
  if n<=0xff: return n
  else:       return n // 0x100
  
class Plane(object):
  """Class for cartesian plane.

  Use sample:
    >>> from pycart import Plane
    >>> piano = Plane()
  
  """
  def __init__(self, name="Cartesian Plane",
               w=600, h=400, sx=1, sy=1, parent=None):
    """
    @param name: Name of L{Plane<Plane>}
    @type  name: C{str}
    @param w: width of L{Plane<Plane>}
    @type  w: C{int}
    @param h: height of L{Plane<Plane>}
    @type  h: C{int}
    @param sx: x scale
    @type  sx: C{int}
    @param sy: y scale
    @type  sy: C{int}
    @param parent: canvas parent
    @type  parent: C{tkinter.Tk}
    """
    
    if parent:
      self._parent = parent
    else:
      self._parent = tk.Tk()
      self._parent.geometry(str(w+2)+'x'+str(h+2)+'+0+0')
      self._parent.title(name)
    self._canvas=tk.Canvas(self._parent, bg='white', width=w, height=h)
    if not parent:
      self._canvas.pack(side=tk.TOP, expand=1, fill=tk.BOTH)
    self._o=(w / 2, h / 2)
    self._s=(sx, sy)
    self._p=(0, 0)
    self._width = 1
    self._color = 'black'
    self._grid=0
    self._canvaswidth=w
    self._canvasheight=h
    self._canvas.update()

  def _p2s(self, p):
    """Translate point coords to pixel coords.

    @param p: Point to translate
    @type  p: C{tuple} (px, py)
    """
    px, py = p
    ox, oy = self._o
    sx, sy = self._s
    return px*sx+ox, -py*sy+oy

  def _r2s(self, r):
    """Translate rectangular coords to screen region coords.

    @param r: Rect to translate
    @type  p: C{tuple} (ax, ay, bx, by)
    """
    ax, ay, bx, by = r
    ox, oy = self._o
    sx, sy = self._s
    return ax*sx+ox, -ay*sy+oy, bx*sx+ox, -by*sy+oy

  def _s2p(self, ps):
    """Translate pixel coords to point coords."""
    xs, ys = ps
    ox, oy = self._o
    sx, sy = self._s
    return (float(xs)-ox)/sx, (float(-ys)+oy)/sy

  def _s2r(self, rs):
    """Translate screen region coords to rectangular coords."""
    axs, ays, bxs, bys = rs
    ox, oy = self._o
    sx, sy = self._s
    return (float(axs)-ox)/sx, \
           (float(-ays)+oy)/sy, \
           (float(bxs)-ox)/sx, \
           (float(-bys)+oy)/sy

  def _s2x(self, xs):
    """Translate screen xcoord to point xcoord."""
    ox, oy = self._o
    sx, sy = self._s
    return (float(xs) - ox) / sx

  def _s2y(self, ys):
    """Translate screen ycoord to point ycoord."""
    ox, oy = self._o
    sx, sy = self._s
    return (float(-ys) + oy) / sy

  def _x2s(self, x):
    """Translate point xcoord to screen xcoord."""
    ox, oy = self._o
    sx, sy = self._s
    return int(round(x*sx+ox))

  def _y2s(self, y):
    """Translate point ycoord to screen ycoord."""
    ox, oy = self._o
    sx, sy = self._s
    return int(round(-y*sy+oy))

  def version(self):
    return _pycart_version

  def mainloop(self):
    self._canvas.mainloop()

  def after(self, *args):
    self._canvas.after(*args)

  def delete(self, *args):
    self._canvas.delete(*args)

  def seto(self, o):
    """Set coords origin posistion."""
    self._o=o
  def geto(self):
    """Get coords origin posistion."""
    return self._o
  
  def sets(self, s):
    """Set x-y unit scale."""
    self._s=s
  def gets(self):
    """Get x-y unit scale."""
    return self._s
  
  def setwidth(self, width):
    """Set pen width."""
    self._width = float(width)
  def getwidth(self):
    """Get pen width."""
    return self._width

  def getcanvaswidth(self):
    """Get canvas width."""
    return self._canvaswidth

  def getcanvasheight(self):
    """Get canvas height."""
    return self._canvasheight

  def getcanvas(self):
    """Get canvas."""
    return self._canvas

  def grid(self, **params):
    """Espose grid."""
    self._canvas.grid(**params)

  def color(self, arg):
    """ Set the pen color.

    Three input formats are allowed:

        color(s)
        s is a Tk specification string, such as "red" or "#f5a938"

        color((r, g, b))
        *a tuple* of r, g, and b, which represent, an RGB color,
        and each of r, g, and b are in the range [0..1]

    Example:

    >>> print color('orange')
    >>> print color('#c0c0c0')
    >>> print color(0.2, 0.8, 0.55)

    """
    if len(arg)==1: arg=arg[0]
    if type(arg) == str:
      if arg[0]=='#':
        if re.match(_RENUMERO, arg):
          colorvalue=arg
        else:
          raise pyerr.ColorError, "bad color string: %s" % arg
      else:
        try:
          rgb=[_inff(nc) for nc in self._canvas.winfo_rgb(arg)]
          colorvalue="#%02x%02x%02x" % tuple(rgb)
        except:
          raise pyerr.ColorError, "bad color name: %s" % arg
    elif type(arg)==tuple:
      try:
        r, g, b = arg
      except:
        raise pyerr.ColorError, "bad color arguments: %s" % (arg,)
      assert 0 <= r <= 1
      assert 0 <= g <= 1
      assert 0 <= b <= 1
      x = 255.0
      y = 0.5 # to round numbers
      colorvalue="#%02x%02x%02x" % (int(r*x+y), int(g*x+y), int(b*x+y))
    else:
      raise pyerr.ColorError, "bad color argument: %s" % arg
    return colorvalue

  def setcolor(self, *args):
    self._color = self.color(args)
  def getcolor(self):
    return self._color

  def clear(self):
    """Clear all graphic window."""
    for i in self._canvas.find_all():
      self._canvas.delete(self, i)
    self._canvas.update()

  def reset(self):
    """Clear graphic window and draw axes."""
    self.clear()
    self.axes()
    
  def jump(self, p):
    """Set pen position to p."""
    self._p=p

  def setpos(self, p):
    """Set pen position to p and draw line from last position to p."""
    self._canvas.create_line(self._p2s(self._p), self._p2s(p),
                             width=self._width, capstyle='round',
                             fill=self._color)
    self._p=p
    self._canvas.update()

  def getpos(self):
    """Get pen position."""
    return self._p

  def save(self, filename):
    """Save graphic window to png or ps file."""
    nfps=filename+'.ps'
    nfpng=filename+'.png'
    self._canvas.postscript(file=nfps)
    err=os.spawnlp(os.P_WAIT, 'convert', 'convert',
                   nfps, 'png:' + filename+'.png')
    if not err: os.spawnlp(os.P_WAIT, 'rm', 'rm', nfps)

  def drawpoint(self, p=None):
    """Draw a point in p or pen position."""
    if p==None: p = self._p
    x, y = self._p2s(p)
    d=self._width
    _id=self._canvas.create_oval((x-d, y-d, x+d, y+d), fill=self._color)
    self._canvas.update()
    return _id

  def drawsegment(self, p0, p1=None):
    """Draw a segment from p0 to p1 or from pen position to p0."""
    if p1==None: p0, p1 = self._p, p0
    x0, y0 = self._p2s(p0)
    x1, y1 = self._p2s(p1)
    _id=self._canvas.create_line(x0, y0, x1, y1,
                                 width=self._width, capstyle='round',
                                 fill=self._color)
    self._canvas.update()
    return _id

  def _getv(self, v):
    vv=[]
    for x, y in v:
      vv.append(self._x2s(x))
      vv.append(self._y2s(y))
    return tuple(vv)

  def drawpoly(self, vertices, _intern=''):
    """Draw polygon from data vertices, move pen."""
    _id=self._canvas.create_polygon(self._getv(vertices), outline=self._color,
                                   width=self._width, fill=_intern) 
    self.jump(vertices[0])
    self._canvas.update()
    return _id

  def axes(self, grid=None):
    """Draw cartesian plane axes."""
    w=int(self._canvas['width']); h=int(self._canvas['height'])
    ox, oy=self._o; sx, sy=self._s
    self._canvas.create_line(0, oy, w, oy, fill=self._color)
    self._canvas.create_line(w-10, oy-5, w, oy, fill=self._color)
    self._canvas.create_line(w-10, oy+5, w, oy, fill=self._color)
    if sx>2:
      da=oy - 1; a=oy + 2
      for i in range(ox, 0, -sx):
        self._canvas.create_line(i, da, i, a, fill=self._color)
      for i in range(ox, w, sx):
        self._canvas.create_line(i, da, i, a, fill=self._color)
    self._canvas.create_line(ox, h, ox, 0, fill=self._color)
    self._canvas.create_line(ox - 5, 10, ox, 0, fill=self._color)
    self._canvas.create_line(ox + 5, 10, ox, 0, fill=self._color)
    if sy>2:
      da=ox - 1; a=ox + 2
      for i in range(oy, 0, -sy):
        self._canvas.create_line(da, i, a, i, fill=self._color)
      for i in range(oy, h, sy):
        self._canvas.create_line(da, i, a, i, fill=self._color)
    if grid<>None: self._grid=grid
    if self._grid and sx>2 and sy>2:
      for i in range(ox-sx, 0, -sx):
        for j in range(oy-sy, 0, -sy):
          self._canvas.create_line(i, j, i+1, j+1, fill=self._color)
        for j in range(oy+sy, h, sy):
          self._canvas.create_line(i, j, i+1, j+1, fill=self._color)
      for i in range(ox+sx, w, sx):
        for j in range(oy-sy, 0, -sy):
          self._canvas.create_line(i, j, i+1, j+1, fill=self._color)
        for j in range(oy+sy, h, sy):
          self._canvas.create_line(i, j, i+1, j+1, fill=self._color)
    self._canvas.update()

if __name__=="__main__":
  p=Plane(sx=20, sy=30)
  p.axes(True)
  p.setcolor('dark khaki')
  p.setwidth(5)
  p.jump((6, -2))
  p.drawpoint()
#  p.setcolor('565656')
  p.setcolor('peachpuff')
  p.setcolor('olive drab')
  p.setcolor('DarkOrchid4')
  p.setcolor('grey84')
  p.setcolor('orange')
  p.setcolor('#c0c0c0')
  p.setcolor(0.2, 0.8, 0.55)
  p.setcolor((0.2, 0.8, 0.55))
#  p.setcolor((0.8, 0.55))
#  p.setcolor((2, 0.8, 0.55))
  p.drawpoint((-6, -2))
  p.setcolor('#228b22')
  p.setwidth(3)
  p.drawsegment((-4, 5))
  p.drawsegment((-4, 5), (-3, 1))
  p.setcolor(0.5, 0.6, 0.7)
  p.setwidth(10)
  p.drawpoly(((-4, -1), (-1, -5), (3, -3), (1, -1)))
  p.mainloop()
